package org.nightstudio.common.face.action.spi;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.Path.Node;

import org.apache.log4j.Logger;
import org.hibernate.validator.engine.MessageInterpolatorContext;
import org.hibernate.validator.engine.ValidatorFactoryImpl;
import org.hibernate.validator.engine.ValueContext;
import org.nightstudio.common.face.action.bind.NightStudioBindingErrorProcessor;
import org.nightstudio.common.util.exception.sys.NightStudioException;
import org.nightstudio.common.util.exception.sys.SystemInternalException;
import org.nightstudio.common.util.lang.ObjectUtil;
import org.nightstudio.common.util.request.RequestUtil;
import org.nightstudio.common.util.valid.ValidUtil;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.servlet.ModelAndView;

public abstract class NightStudioPostAction<T> extends NightStudioAction {
	private Logger logger = Logger.getLogger(this.getClass());
	private static ValidatorFactory factory = ValidUtil.getValidatorFactory();
	protected static Validator validator = ValidUtil.getValidator();
	protected static NightStudioBindingErrorProcessor bindingErrorProcessor = new NightStudioBindingErrorProcessor();
	@SuppressWarnings("unchecked")
	private Class<T> tClazz = ObjectUtil.getActualTypeArguments(this.getClass(), 0);
	
	protected T getFormObject(HttpServletRequest request) throws Exception {
		return ObjectUtil.newInstance(tClazz);
	}
	
	protected abstract T doInitCommand(HttpServletRequest request, T command) throws Exception;
	
	protected abstract void doReferenceForm(HttpServletRequest request,
			Object command, Errors errors, Map<String, Object> model)
			throws Exception;
	
	protected abstract void doHandleSubmit(HttpServletRequest request,
			HttpServletResponse response, T command,
			Map<String, Object> model) throws Exception;

	protected Set<ConstraintViolation<T>> validateCommand(T obj) {
		return validator.validate(obj);
	}
	
	protected Set<ConstraintViolation<T>> validateCommand(T obj, String[] properties) {
		Set<ConstraintViolation<T>> violations = validator.validate(obj);
		Set<ConstraintViolation<T>> result = new HashSet<ConstraintViolation<T>>();
		Iterator<Node> it;
		String nodeName;
		boolean find;
		for (ConstraintViolation<T> violation : violations) {
			it = violation.getPropertyPath().iterator();
			find = false;
			while (it.hasNext()) {
				nodeName = it.next().getName();
				for (String property : properties) {
					if (property == null) {
						if (nodeName == null) {
							result.add(violation);
							find = true;
							break;
						}
					} else if (property.equals(nodeName)) {
						result.add(violation);
						find = true;
						break;
					}
				}
				if (find) {
					break;
				}
			}
		}
		return result;
	}
	
	protected Set<ConstraintViolation<T>> validateValue(String fieldName, Object fieldValue) {
		return validator.validateValue(tClazz, fieldName, fieldValue);
	}
	
	protected ModelAndView doHandleError(HttpServletRequest request,
			HttpServletResponse response, T command, BindException errors) throws Exception {
		return this.showForm(request, response, errors);
	}
	
	protected List<String> getErrorMessages(BindException errors) {
		List<String> result = new ArrayList<String>();
		if (errors.getAllErrors() != null) {
			for (Object error : errors.getAllErrors()) {
				if (error instanceof ObjectError) {
					result.add(((ObjectError) error).getDefaultMessage());
				}
			}
		}
		return result;
	}
	
	@Override
	protected ServletRequestDataBinder createBinder(HttpServletRequest request,
			Object command) throws Exception {
		ServletRequestDataBinder binder = super.createBinder(request, command);
		binder.setBindingErrorProcessor(bindingErrorProcessor);
		return binder;
	}
	
	@Override
	protected T formBackingObject(HttpServletRequest request)
			throws Exception {
		this.transformParameters(request);
		T obj = this.getFormObject(request);
		if (obj == null) {
			obj = ObjectUtil.newInstance(tClazz);
		}
		ServletRequestDataBinder binder = createBinder(request, obj);
		binder.bind(request);
		obj = this.doInitCommand(request, obj);
		binder.bind(request);
		RequestUtil.setCommand(request, obj);
		return obj;
	}
	
	@Override
	protected Map<String, Object> referenceData(
			HttpServletRequest request, Object command, Errors errors)
			throws Exception {
		this.transformParameters(request);
		Map<String, Object> model = new HashMap<String, Object>();
		this.transformParameters(request, model);
		doReferenceForm(request, command, errors, model);
		return model;
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	protected ModelAndView onSubmit(HttpServletRequest request,
			HttpServletResponse response, Object command, BindException errors)
			throws Exception {
		T obj = (T) command;
		try {
			RequestUtil.setCommand(request, command);
			Map<String, Object> model = new HashMap<String, Object>();
			this.transformParameters(request);
			this.transformParameters(request, model);
			Set<ConstraintViolation<T>> violations = this.validateCommand(obj);
			if (ValidUtil.hasViolation(violations)) {
				for (ConstraintViolation violation : violations) {
					MessageInterpolatorContext context = new MessageInterpolatorContext(
							violation.getConstraintDescriptor(), 
							ValueContext.getLocalExecutionContext(obj).getCurrentValidatedValue());
					String message = ((ValidatorFactoryImpl) factory).getMessageInterpolator().interpolate(
									violation.getMessageTemplate(), 
									context,
									request.getLocale());
					errors.rejectValue(violation.getPropertyPath().toString(),	null, message);
				}
			}
			if (errors.hasErrors()) {
				return this.doHandleError(request, response, obj, errors);
			}
			this.doHandleSubmit(request, response, obj, model);
			return this.getSuccessModelAndView(request, model);
		} catch (Throwable t) {
			logger.warn("MimsPostAction handle throwable: ", t);
			NightStudioException e = null;
			if (t instanceof NightStudioException) {
				e = (NightStudioException) t;
			} else {
				e = new SystemInternalException();
			}
			errors.reject(e.getErrorCode().getValue(), e.getArgs(), null);
			return this.doHandleError(request, response, obj, errors);
		}
	}
}
